﻿namespace CloudNinja.Metering.Data.Dapper
{
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    using CloudNinja.Metering.Data.Models;

    using global::Dapper;

    using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling;

    public class MeteringDataRepository : IMeteringDataRepository, IMeteringReporting, IApiKeyStore
    {
        private readonly string connectionString;

        private readonly RetryPolicy connectionRetryPolicy;

        private const string WriteQuery = @"Upsert_MeterData";

        private float retryCount;

        private TimeSpan getRetryConnectionDueTime;

        private RetryPolicy commandRetryPolicy;

        public MeteringDataRepository(string connectionString, RetryPolicy retryPolicy)
        {
            this.connectionString = connectionString;
            this.connectionRetryPolicy = retryPolicy;
            this.commandRetryPolicy = retryPolicy;
        }

        public void Write(string meterTypeId, string tenantId, double value, DateTimeOffset timeStamp, int count)
        {
            this.Write(
                new[]
                    {
                        new MeterData
                            {
                                MeterTypeId = meterTypeId,
                                TenantId = tenantId,
                                Value = value,
                                TimePeriod = timeStamp.UtcDateTime,
                                Count = count
                            }
                    });
        }

        public void Write(MeterData dataPoint)
        {
            this.Write(new[] { dataPoint });
        }

        public void Write(IEnumerable<MeterData> dataPoints)
        {
            using (var connection = new ReliableSqlConnection(this.connectionString, this.connectionRetryPolicy, this.commandRetryPolicy))
            {
                connection.Open();
                DynamicParametersForTvp parameter;
                if (PrepareTvp(dataPoints, out parameter))
                {
                    return;
                }

                connection.Execute(WriteQuery, parameter, commandType: CommandType.StoredProcedure);
            }
        }

        public async Task WriteAsync(MeterData dataPoint)
        {
            await this.ExecuteWriteAsync(new[] { dataPoint });
        }

        public async Task WriteAsync(IEnumerable<MeterData> dataPoints)
        {
            await this.ExecuteWriteAsync(dataPoints);
        }

        public bool IfExists(MeterData meterdata)
        {
            throw new NotImplementedException();
        }

        public async Task<IEnumerable<MonthlyView>> GetMonthlyViewAsync()
        {
            using (var connection = new ReliableSqlConnection(this.connectionString, this.connectionRetryPolicy, this.commandRetryPolicy))
            {
                connection.Open();
                return
                    await
                    connection.QueryAsync<MonthlyView>(
                        "select TenantId, MeterTypeId, Year, Month, TimePeriodEnd, Value from MonthlyView");
            }
        }

        public async Task<IEnumerable<TotalResourceUsage>> GetTotalResourceUsageAsync()
        {
            using (var connection = new ReliableSqlConnection(this.connectionString, this.connectionRetryPolicy, this.commandRetryPolicy))
            {
                connection.Open();
                return
                    await
                    connection.QueryAsync<TotalResourceUsage>(
                        "select TenantId, Year, Month, TimePeriodEnd, [TotalDbs], [TotalStg], [TotalBwe], [TWU] from [TotalResourceUsageView]");
            }
        }

        public async Task<IEnumerable<MeterData>> GetMeterDataAsync()
        {
            using (var connection = new ReliableSqlConnection(this.connectionString, this.connectionRetryPolicy, this.commandRetryPolicy))
            {
                connection.Open();
                return
                    await
                    connection.QueryAsync<MeterData>(
                        "select [MeterTypeId], [TenantId], [TimePeriod], [Value], [Count] from [MeterData]");
            }
        }

        public async Task<IEnumerable<TotalDailyView>> GetTotalDailyViewAsync()
        {
            using (var connection = new ReliableSqlConnection(this.connectionString, this.connectionRetryPolicy, this.commandRetryPolicy))
            {
                connection.Open();
                return
                    await
                    connection.QueryAsync<TotalDailyView>(
                        "select [MeterTypeId], [TimePeriod], [Value] from [TotalDailyView]");
            }
        }

        public async Task<IEnumerable<TotalMonthlyView>> GetTotalMonthlyViewAsync()
        {
            using (var connection = new ReliableSqlConnection(this.connectionString, this.connectionRetryPolicy, this.commandRetryPolicy))
            {
                connection.Open();
                return
                    await
                    connection.QueryAsync<TotalMonthlyView>(
                        "select MeterTypeId, [Year], [Month], TimePeriodEnd, [Value] from [TotalMonthlyView]");
            }
        }

        public byte[] GetServiceKey(string userId)
        {
            using (var connection = new ReliableSqlConnection(this.connectionString, this.connectionRetryPolicy, this.commandRetryPolicy))
            {
                connection.Open();
                var serviceKeys =
                    (connection.Query<ServiceKey>(
                        "select UserId, [Key] from [ServiceKey] where UserId = @UserId",
                        new { UserId = userId })).ToList();
                return serviceKeys.Count() != 1 ? null : Encoding.ASCII.GetBytes(serviceKeys.First().Key);
            }
        }

        private async Task ExecuteWriteAsync(IEnumerable<MeterData> dataPoints)
        {
            using (var connection = new ReliableSqlConnection(this.connectionString, this.connectionRetryPolicy, this.commandRetryPolicy))
            {
                connection.Open();
                DynamicParametersForTvp parameter;
                if (PrepareTvp(dataPoints, out parameter))
                {
                    return;
                }

                await
                    connection.ExecuteAsync(
                        WriteQuery,
                        parameter,
                        commandType: CommandType.StoredProcedure);
            }
        }

        private static bool PrepareTvp(IEnumerable<MeterData> dataPoints, out DynamicParametersForTvp parameter)
        {
            var meterDataEntities = dataPoints.ToList();
            if (meterDataEntities.Count <= 0)
            {
                parameter = null;
                return true;
            }

            var dataTable = new DataTable();
            dataTable.Columns.Add("MeterTypeId", typeof(string));
            dataTable.Columns.Add("TenantId", typeof(string));
            dataTable.Columns.Add("TimePeriod", typeof(DateTime));
            dataTable.Columns.Add("Value", typeof(double));
            dataTable.Columns.Add("Count", typeof(int));

            foreach (var meterData in meterDataEntities)
            {
                dataTable.Rows.Add(
                    meterData.MeterTypeId,
                    meterData.TenantId,
                    meterData.TimePeriod,
                    meterData.Value,
                    meterData.Count < 1 ? 1 : meterData.Count);
            }

            parameter = new DynamicParametersForTvp();
            parameter.Add(new TableValueParameter("@MeterDataTempTable", "MeterDataTypeVal", dataTable));
            return false;
        }
    }
}